#ifndef _CHAIN_C
#define _CHAIN_C

#include "_Chain.h"

/* Constructor */
void
_initChain(_Chain *initThis, int type)
{
    initThis->type = type;
    initThis->firstNode = NULL;
    initThis->previousNode = NULL;
    initThis->lastNode = NULL;
    initThis->length = 0;
}

/* Destructor */
void 
_deleteChain(_Chain *deleteThis)
{
    _Node *toDelete = NULL;
    _Node *temp = NULL;
    
    if(deleteThis != NULL) {
        toDelete = deleteThis->firstNode;

        while(toDelete != NULL) { 
            temp = toDelete->rightNode;
            free(toDelete);
            toDelete = temp;
        }
    }
}

void 
_ChainAdd(_Chain *c, void *addThis)
{
    _Node *newNode = _newNode(addThis);

    if(c->length == 0) {
        c->firstNode = newNode;
        c->previousNode = newNode;
        c->lastNode = newNode;
    } else {
        c->lastNode->rightNode = newNode;
        newNode->leftNode = c->lastNode;
        c->lastNode = newNode;
    }

    c->length++;
}

void *
_ChainGet(_Chain *c, size_t index)
{
    _Node *getNode = __ChainNodeGet(c, index);

    /* Taking care of index out of bounds situation */
    if(getNode != NULL) {
        return getNode->data;   
    } else {
        return NULL;
    }
}

void *
_ChainSet(_Chain *c, size_t index, void *setThis)
{
    _Node *swapNode = __ChainNodeGet(c, index);
    void *toReturn = NULL;

    /* If index is out of bounds, _NodeGet will return NULL to swapNode */
    if(swapNode != NULL) {
        toReturn = swapNode->data;
        swapNode->data = setThis;
    }

    return toReturn;
}

void 
_ChainInsert(_Chain *c, size_t index, void *insertThis)
{
    _Node *newNode = NULL;
    _Node *insertNode = NULL;

    /* Inserting at the beginning of the chain */
    if(index == 0) { 
        _ChainPushFront(c, insertThis);

    /* Inserting at the end of the chain */
    } else if(index == c->length) {
        _ChainAdd(c, insertThis);

    /* Inserting somewhere in the middle of the chain */
    } else {
        newNode = _newNode(insertThis);
        insertNode = __ChainNodeGet(c, index);

        /* If index is out of bounds _NodeGet will have returned NULL */
        if(insertNode != NULL) {
            newNode->leftNode = insertNode->leftNode;
            newNode->rightNode = insertNode;
            insertNode->leftNode->rightNode = newNode;
            insertNode->leftNode = newNode;
            c->length++;
        } else {
            free(newNode);
        }
    }
}

void *
_ChainExtract(_Chain *c, size_t index)
{
    _Node *extractNode = NULL;
    void *toReturn = NULL;

    /* Removing first element */
    if(index == 0) {
        return _ChainPopFront(c);

    /* Removing last element */
    } else if(index == c->length - 1) {
        return _ChainPopBack(c);

    /* Removing some middle element */
    } else {
        extractNode = __ChainNodeGet(c, index);

        /* If index is out of bounds _NodeGet will have returned NULL */
        if(extractNode != NULL) {
            toReturn = extractNode->data;

            extractNode->leftNode->rightNode = extractNode->rightNode;
            extractNode->rightNode->leftNode = extractNode->leftNode;

            free(extractNode);
            c->length--;
        }

        return toReturn;
    }
}

void *
_ChainPopFront(_Chain *c)
{
    _Node *firstNode = c->firstNode;
    void *toReturn = NULL;

    /* If the _Chain is not empty */
    if(firstNode != NULL) {
        toReturn = firstNode->data;
        c->firstNode = firstNode->rightNode;

        if(c->firstNode != NULL) {
            c->firstNode->leftNode = NULL;
        }

        free(firstNode);
        c->length--;
    }
    
    return toReturn;
}

void *
_ChainPopBack(_Chain *c)
{
    _Node *lastNode = c->lastNode;
    void *toReturn = NULL;

    if(lastNode != NULL) {
        toReturn = lastNode->data;
        c->lastNode = c->lastNode->leftNode;

        if(c->lastNode != NULL) {
            c->lastNode->rightNode = NULL;
        } else {
            c->firstNode = NULL;
        }

        free(lastNode);
        c->length--;
    }

    return toReturn;
}

void 
_ChainPushFront(_Chain *c, void *pushThis)
{
    _Node *newNode = NULL;

    if(c->length == 0) {
        _ChainAdd(c, pushThis);
    } else {
        newNode = _newNode(pushThis);

        c->firstNode->leftNode = newNode;
        newNode->rightNode = c->firstNode;
        c->firstNode = newNode;

        c->length++;
    }
}

void 
_ChainPushBack(_Chain *c, void *pushThis)
{
    _ChainAdd(c, pushThis);
}

/* TODO: Update this to use previousNode */
_Node *
__ChainNodeGet(_Chain *c, size_t index)
{
    _Node *toReturn = NULL;
    size_t i;

    if(_boundsCheck(c, index)) {

        /* It's faster to start at firstNode */
        if(index < c->length - 1 - index) {
            toReturn = c->firstNode;

            for(i = 0; i < index; ++i) {
                toReturn = toReturn->rightNode;
            }

        /* It's faster to start at lastNode */
        } else {
            toReturn = c->lastNode;

            for(i = c->length - 1; i > index; --i) {
                toReturn = toReturn->leftNode;
            }
        }
    } else {
        _indexError(index);
    }   

    return toReturn;
}

#endif
